package work.chenbo.spring.start.http.core;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import work.chenbo.spring.start.http.properties.HttpClientPoolProperties;

import javax.net.ssl.SSLContext;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @author ChenBo
 * @className HttpClientAutoConfiguration
 * @date 2019/11/19
 */
@Configuration
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@EnableConfigurationProperties(HttpClientPoolProperties.class)
@Slf4j
public class HttpClientAutoConfiguration {

    private final HttpClientPoolProperties poolProperties;

    public HttpClientAutoConfiguration(HttpClientPoolProperties poolProperties) {
        this.poolProperties = poolProperties;
    }


    /**
     *
     * @author ChenBo
     * @date 2019/11/19
     */
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        RestTemplate restTemplate = new RestTemplate(factory);

        HttpMessageConverter<?> converter = null;
        List<HttpMessageConverter<?>> messageConverterList = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> messageConverter : messageConverterList){
            if (StringHttpMessageConverter.class.equals(messageConverter.getClass())){
                converter = messageConverter;
                break;
            }
        }

        if(converter !=null){
            messageConverterList.remove(converter);
        }

        Charset charset = Charset.forName(poolProperties.getCharset());
        messageConverterList.add(1,new StringHttpMessageConverter(charset));
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        log.info("初始化RestTemple成功!!!");

        return restTemplate;
    }


    /**
     * 创建HTTP客户端工厂
     * @author ChenBo
     * @date 2019/11/19
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient){
        if(poolProperties.getMax_total_connect() <= 0){
            throw new IllegalArgumentException("invalid max_total_connect:"+poolProperties.getMax_total_connect());
        }
        if(poolProperties.getMax_connect_route() <= 0){
            throw new IllegalArgumentException("invalid max_total_route:"+poolProperties.getMax_connect_route());
        }

        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(poolProperties.getConnect_timout());
        factory.setReadTimeout(poolProperties.getRead_timeout());
        return factory;
    }


    @Bean
    public HttpClient httpClient(){
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        try{
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (chain, authType) -> true).build();//设置信任ssl访问
            httpClientBuilder.setSSLContext(sslContext);
            NoopHostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())// 注册http请求
                    .register("https", sslConnectionSocketFactory)// 注册https请求
                    .build();

            /*使用HttpClient连接池的方式配置(推荐)，同时支持netty，okHttp以及其他http框架*/
            PoolingHttpClientConnectionManager poolManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            poolManager.setMaxTotal(poolProperties.getMax_total_connect());//最大连接数
            poolManager.setDefaultMaxPerRoute(poolProperties.getMax_connect_route());//同路由并发数

            httpClientBuilder.setConnectionManager(poolManager); //配置连接池
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(poolProperties.getRetry_times(),true)); // 重试次数
            httpClientBuilder.setDefaultHeaders(this.defaultHeaders());  //设置默认请求头
            if(poolProperties.getKeep_alive_target_host()!=null && poolProperties.getKeep_alive_target_host().size()>0){
                httpClientBuilder.setKeepAliveStrategy(this.connectionKeepAliveStrategy());
            }
            log.info("HttpClient 配置成功!!");
        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            log.error("初始化HTTP连接池出错",e);
        }
        return httpClientBuilder.build();
    }



    /**
     * 默认请求头
     * @return
     */
    private List<Header> defaultHeaders(){
        return new ArrayList<Header>(){{
            add(new BasicHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
            add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
            add(new BasicHeader("Accept-Language", "zh-CN"));
            add(new BasicHeader("Connection", "Keep-Alive"));
        }};
    }

    /**
     * 配置长连接保持策略
     * @return
     */
    private ConnectionKeepAliveStrategy connectionKeepAliveStrategy(){
        return ((response, context) -> {
            BasicHeaderElementIterator elementIterator = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            for(;elementIterator.hasNext();){
                HeaderElement headerElement = elementIterator.nextElement();
                log.info("HeaderElement:{}", headerElement.toString());
                String name = headerElement.getName();
                String value = headerElement.getValue();
                if(StringUtils.isNotBlank(value) && "timeout".equalsIgnoreCase(name)){
                    try{
                        return Long.parseLong(value) * 1000;
                    }catch (NumberFormatException e){
                        log.error("解析长连接过期时间异常",e);
                    }
                }
            }
            HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            Optional<Map.Entry<String, Integer>> httpHostMap = poolProperties.getKeep_alive_target_host().entrySet().parallelStream()
                    .filter(element -> element.getKey().equalsIgnoreCase(target.getHostName()))
                    .findAny();
            return httpHostMap.map(element -> element.getValue() * 1000L).orElse(poolProperties.getKeep_alive_times() * 1000L);
        });
    }

}
